package jsr292.weaver;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.jar.JarEntry;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;

public class Main {
    static class OfflineClassWriter extends ClassWriter {
        private final URLClassLoader classLoader;
        
        public OfflineClassWriter(URLClassLoader classLoader, int flags) {
            super(flags);
            this.classLoader = classLoader;
        }
        
        @Override
        protected String getCommonSuperClass(String type1, String type2) {
            Class<?> c, d;
            try {
                c = classLoader.loadClass(type1.replace('/', '.'));
                d = classLoader.loadClass(type2.replace('/', '.'));
            } catch (Exception e) {
                throw new RuntimeException(e.toString());
            }
            if (c.isAssignableFrom(d)) {
                return type1;
            }
            if (d.isAssignableFrom(c)) {
                return type2;
            }
            if (c.isInterface() || d.isInterface()) {
                return "java/lang/Object";
            } 
            do {
                c = c.getSuperclass();
            } while (!c.isAssignableFrom(d));
            return c.getName().replace('.', '/');
        }
    }
    
    static class OfflineWeaver extends OnlineWeaver {
        public OfflineWeaver(ClassVisitor cv, boolean retransform) {
            super(cv, retransform);
        }
        
        @Override
        public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
            if (version >= Opcodes.V1_7)
                version = Opcodes.V1_6;
            super.visit(version, access, name, signature, superName, interfaces);
        }
    }
    
    private static void weave(URLClassLoader classloader, InputStream input, OutputStream output) throws IOException {
        ClassReader reader = new ClassReader(input);
        ClassWriter writer = new OfflineClassWriter(classloader, ClassWriter.COMPUTE_FRAMES|ClassWriter.COMPUTE_MAXS);

        reader.accept(new OfflineWeaver(writer, false), ClassReader.EXPAND_FRAMES);

        byte[] byteArray = writer.toByteArray();
        //CheckClassAdapter.verify(new ClassReader(byteArray), true, new PrintWriter(System.out));

        output.write(byteArray);
    }
    
    private static void copy(InputStream input, OutputStream output) throws IOException {
        byte[] buffer = new byte[8192];
        int read;
        while((read = input.read(buffer)) != -1) {
            output.write(buffer, 0, read);
        }
    }
    
    private static void weave(URLClassLoader classloader, File file) throws IOException {
        ZipInputStream inputStream = new ZipInputStream(new FileInputStream(file));
        try {
            String name = file.getName();
            File outputFile = new File(file.getParent(), name.substring(0, name.length() - 4) + "-backported.jar"); 
            ZipOutputStream outputStream = new ZipOutputStream(new FileOutputStream(outputFile));
            try {
                ZipEntry entry;
                while ((entry = inputStream.getNextEntry()) != null) {
                    String entryName = entry.getName();
                    outputStream.putNextEntry(new JarEntry(entryName));
                    if (entryName.endsWith(".class")) {
                        weave(classloader, inputStream, outputStream);
                    } else {
                        copy(inputStream, outputStream);
                    }
                }
                outputStream.flush();
                
            } finally {
                outputStream.close();
            }
            
        } finally {
            inputStream.close();
        }
    }
    
    static void weave(File[] jars, File[] dependencies) throws IOException {
        ArrayList<URL> urls = new ArrayList<URL>();
        for(File jar: jars) {
            urls.add(jar.toURI().toURL());
        }
        for(File dependency: dependencies) {
            urls.add(dependency.toURI().toURL());
        }
        URLClassLoader classloader = new URLClassLoader(urls.toArray(new URL[urls.size()]), Main.class.getClassLoader());
        
        for(File jar: jars) {
            weave(classloader, jar);
        }
    }

    public static void main(String[] args) throws IOException {
        if (args.length == 0) {
            System.err.println("synopsis: java -jar jsr292-backport.jar foo.jar foo2.jar ...\n  backport offline weaver, jars taken as arguments also must be in the bootclasspath\n");
            System.exit(1);
            return;
        }
        
        URL[] urls = new URL[args.length];
        for(int i=0; i<args.length; i++) {
            urls[i] = new File(args[i]).toURI().toURL();
        }
        URLClassLoader classloader = new URLClassLoader(urls, Main.class.getClassLoader());
        
        for(String arg: args) {
            weave(classloader, new File(arg));
        }
    }
}
